/*
 * Copyright 2004-2006, Thorbjrn Lindeijer <thorbjorn@lindeijer.nl>
 * Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
 *
 * This file is part of libtiled-java.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package tiled.io.xml;

import java.io.IOException;
import java.io.Writer;
import java.util.Stack;

/**
 * A simple helper class to write an XML file, based on http://www.xmlsoft.org/html/libxml-xmlwriter.html
 */
public class XMLWriter {
	private boolean				bIndent			= true;
	private String				indentString	= " ";
	private String				newLine			= "\n";
	private final Writer		w;

	private final Stack<String>	openElements;
	private boolean				bStartTagOpen;
	private boolean				bDocumentOpen;

	public XMLWriter(Writer writer) {
		this.openElements = new Stack<String>();
		this.w = writer;
	}

	public void setIndent(boolean bIndent) {
		this.bIndent = bIndent;
		this.newLine = bIndent ? "\n" : "";
	}

	public void setIndentString(String indentString) {
		this.indentString = indentString;
	}

	public void startDocument() throws IOException {
		this.startDocument("1.0");
	}

	public void startDocument(String version) throws IOException {
		this.w.write("<?xml version=\"" + version + "\" encoding=\"UTF-8\"?>" + this.newLine);
		this.bDocumentOpen = true;
	}

	public void writeDocType(String name, String pubId, String sysId) throws IOException, XMLWriterException {
		if (!this.bDocumentOpen) {
			throw new XMLWriterException("Can't write DocType, no open document.");
		} else if (!this.openElements.isEmpty()) {
			throw new XMLWriterException("Can't write DocType, open elements exist.");
		}

		this.w.write("<!DOCTYPE " + name + " ");

		if (pubId != null) {
			this.w.write("PUBLIC \"" + pubId + "\"");
			if (sysId != null) {
				this.w.write(" \"" + sysId + "\"");
			}
		} else if (sysId != null) {
			this.w.write("SYSTEM \"" + sysId + "\"");
		}

		this.w.write(">" + this.newLine);
	}

	public void startElement(String name) throws IOException, XMLWriterException {
		if (!this.bDocumentOpen) {
			throw new XMLWriterException("Can't start new element, no open document.");
		}

		if (this.bStartTagOpen) {
			this.w.write(">" + this.newLine);
		}

		this.writeIndent();
		this.w.write("<" + name);

		this.openElements.push(name);
		this.bStartTagOpen = true;
	}

	public void endDocument() throws IOException {
		// End all open elements.
		while (!this.openElements.isEmpty()) {
			this.endElement();
		}

		this.w.flush(); //writers do not always flush automatically...
	}

	public void endElement() throws IOException {
		String name = this.openElements.pop();

		// If start tag still open, end with />, else with </name>.
		if (this.bStartTagOpen) {
			this.w.write("/>" + this.newLine);
			this.bStartTagOpen = false;
		} else {
			this.writeIndent();
			this.w.write("</" + name + ">" + this.newLine);
		}

		// Set document closed when last element is closed
		if (this.openElements.isEmpty()) {
			this.bDocumentOpen = false;
		}
	}

	public void writeAttribute(String name, String content) throws IOException, XMLWriterException {
		if (this.bStartTagOpen) {
			String escapedContent = (content != null) ? content.replaceAll("\"", "&quot;") : "";
			this.w.write(" " + name + "=\"" + escapedContent + "\"");
		} else {
			throw new XMLWriterException("Can't write attribute without open start tag.");
		}
	}

	public void writeAttribute(String name, int content) throws IOException, XMLWriterException {
		this.writeAttribute(name, String.valueOf(content));
	}

	public void writeAttribute(String name, float content) throws IOException, XMLWriterException {
		this.writeAttribute(name, String.valueOf(content));
	}

	public void writeCDATA(String content) throws IOException {
		if (this.bStartTagOpen) {
			this.w.write(">" + this.newLine);
			this.bStartTagOpen = false;
		}

		this.writeIndent();
		this.w.write(content + this.newLine);
	}

	public void writeComment(String content) throws IOException {
		if (this.bStartTagOpen) {
			this.w.write(">" + this.newLine);
			this.bStartTagOpen = false;
		}

		this.writeIndent();
		this.w.write("<!-- " + content + " -->" + this.newLine);
	}

	public void writeElement(String name, String content) throws IOException, XMLWriterException {
		this.startElement(name);
		this.writeCDATA(content);
		this.endElement();
	}

	private void writeIndent() throws IOException {
		if (this.bIndent) {
			for (int i = 0; i < this.openElements.size(); i++) {
				this.w.write(this.indentString);
			}
		}
	}
}
